#pragma once
#include "PxPhysicsAPI.h"
#include "IPhysics.h"
#include "MaterialPhysics.h"
#include "GameObject.h"



namespace Boon
{
	class Messenger;

namespace Physics
{
	
	class SimulationCallback;
	class SimulationFilterCallback;

	/*!
	* Nvidia PhysX implementation of Boon-Physics
	* Managing life cycle, collision detection/handling of GameObjects
	* Responsible for managing and creation of MaterialPhysics
	*/
	class NvidiaPhysX : public IPhysics, public physx::PxErrorCallback
	{
	public:

		struct ActorInfo
		{
			ActorInfo( const GameObjectStrong& go, physx::PxRigidActor* actor, physx::PxD6Joint* selfConstraint )
				: m_GameObject( go ), m_Actor( actor ), m_JointSelfConstraint( selfConstraint ) { }

			GameObjectStrong				m_GameObject;
			physx::PxRigidActor*			m_Actor;
			physx::PxD6Joint*				m_JointSelfConstraint;
		};

		typedef std::pair<GameObjectID, ActorInfo>	MapPairActorPerGO;
		typedef std::map<GameObjectID, ActorInfo>	MapActorPerGameObject;
		typedef std::pair<std::size_t, GameObject&>	MapPairGOPerActor;
		typedef std::map<std::size_t, GameObject&>	MapGOPerActor;

		NvidiaPhysX(void);
		virtual ~NvidiaPhysX(void);

		static std::size_t ActorPtrToID( physx::PxActor* actor ) {	return reinterpret_cast<std::size_t>( actor ); }

		/*! \brief		First function to call for initializing physics world
			\return		bool - True on success
		*/
		virtual bool			Init();

		virtual bool			Unload();

		virtual void			Update();

		virtual void			SyncGameObjects();

		virtual void			AddForce( const Rigidbody& rigidbody, const Vector3& force, ForceMode mode );

		virtual void			AddForce( const Rigidbody& rigidbody, float x, float y, float z, ForceMode mode );

		virtual const			Vector3 GetVelocity( const GameObjectID& id ) const;

		virtual void			SetVelocity( const GameObjectID& id, const Vector3& velocity );

		virtual void			Translate( const GameObjectID& id, const Vector3& direction );

		virtual const			Matrix44 GetTransform( const GameObjectID& id ) const;

		virtual void			SetTransform( const Transform& t );

		virtual void			RemoveGameObject( const GameObjectID& g );

		virtual void			IgnoreCollision( const GameObjectID& a, const GameObjectID& b, bool enabled );

		virtual void			OverlapSphere( const Vector3& position, float radius, ColliderResult& outResult );

		virtual bool			RayCast( const Vector3& origin, const Vector3 direction, RaycastHit& outHit, float distance, bool staticOnly = true );

		virtual void			reportError( physx::PxErrorCode::Enum code, const char* message, const char* file, int line );

		virtual MaterialPhysics CreateMaterial( float frictionSt, float frictionDyn, float restitution );

		virtual bool			AddRigidbody( Rigidbody& rigidbody );

		bool					RemoveRigidbody( const GameObjectID id );

		bool					UpdateRigidbody( Rigidbody& rigidbody );

		/*! \brief		Adds a collider to a rigidbody or a static actor
			\param		collider - Collider-Component added to a game object
			\return		bool - True on success
		*/
		virtual bool			AddCollider( Collider& collider );

		bool					UpdateCollider( Collider& collider );
	private:

		/*! \brief		Will be called from AddColliderAny() if there is not rigidbody attached to add a static collider to the scene
			\return		bool - True on success
		*/
		bool					CreateStaticActor( const physx::PxGeometry& geometry, const physx::PxVec3& position, const physx::PxQuat& rotation, const GameObjectID id, Collider& collider );

		/*! \brief		Creates a StaticActor-Collider if there is no rigidbody attached to the GameObject
						Creates a RigidBody-Collider if there is a rigidbody attached
			\return		bool - True on success
		*/
		bool					AddColliderAny( Collider& collider, const physx::PxGeometry& geometry, const physx::PxVec3& position, const physx::PxQuat& rotation );

		bool					UpdateColliderAny( Collider& collider, const physx::PxGeometry& geometry, const physx::PxVec3& position, const physx::PxQuat& rotation );

		void					OnRigidbodyEvent( const IMessengerEventStrong& edata );
		void					OnColliderEvent( const IMessengerEventStrong& edata );


	
		MapActorPerGameObject					m_Actors;
		MapGOPerActor					m_GameObjects;
		physx::PxPhysics*						m_Physics;
		physx::PxScene*							m_Scene;
		physx::PxFoundation*					m_Foundation;
		MaterialPhysics							m_MaterialDefault;
		physx::PxDefaultCpuDispatcher*			m_CpuDispatcher;
		//physx::pxtask::CudaContextManager*	m_CudaContextMngr;
		Messenger*								m_Messenger;
		Shared_ptr<SimulationCallback>			m_Sim_Callback;
		Shared_ptr<SimulationFilterCallback>	m_Sim_Filter_Callback;
		float									m_TimeStep;

		friend									SimulationCallback;

		struct SqRayBuffer
		{
			SqRayBuffer( physx::PxScene* scene, physx::PxU32 numRays, physx::PxU32 numHits);
			~SqRayBuffer();
			physx::PxBatchQuery*				m_BatchQuery;

			physx::PxRaycastQueryResult*		m_RayCastResults;
			physx::PxRaycastHit*				m_RayCastHits;

			physx::PxU32 						m_QueryResultSize; 
			physx::PxU32 						m_HitSize;
			void*								m_OrigAddresses[2];
		};

		Shared_ptr<SqRayBuffer>					m_RayBuffer;

	};


}
}
